//------------------------------------------------------------------------
//
//	CCGameBox is a game development library for iPhone and iPod Touch.
//
//	Licensed under the BSD license, see LICENSE in root for details.
// 
//	Copyright (c) 2009 James Hui (a.k.a. Dr.Watson)
// 
//	For latest updates, please visit http://www.cocoachina.com
//
//------------------------------------------------------------------------

#include <math.h>

#include "CCRenderBox.h"
#include "CCTexture.h"
#include "CCImage.h"
#include "Wrapper.h"

static int mVertexCount = 0;
static GLfloat mVertices[MAX_VERTEX_BUFFER<<1];		// vertex buffer for batch rendering
static GLfloat mUVs[MAX_VERTEX_BUFFER<<1];			// UV buffer for batch rendering
static GLfloat mColors[MAX_VERTEX_BUFFER<<2];		// colour buffer for batch reendering


static void InitPerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar)
{
    GLfloat xmin, xmax, ymin, ymax;
	
    ymax = zNear * (GLfloat)tan(fovy * 3.1415962f / 360.0);
    ymin = -ymax;
    xmin = ymin * aspect;
    xmax = ymax * aspect;
	
    glFrustumf(xmin, xmax, ymin, ymax, zNear, zFar);
}


CCRenderBox* CCRenderBox::mInstance = 0;


CCRenderBox* CCRenderBox::GetInstance()
{
	if (mInstance == 0)
	{
		mInstance = new CCRenderBox();
		mInstance->Init();
	}
	
	return mInstance;
}


void CCRenderBox::Destroy()
{
	if (mInstance)
	{
		mInstance->ShutDown();
		delete mInstance;
		mInstance = 0;
	}
}

CCRenderBox::CCRenderBox()
{
	mCurrentTex = -1;
	mLinearFiltering = true;
	mUseColorPointer = false;
	
	//mScreenWidth = 320;
//	mScreenHeight = 480;
	mScreenWidth = 480;
	mScreenHeight = 320;
	
}


CCRenderBox::~CCRenderBox()
{
	
}


void CCRenderBox::EnableLinearFiltering(bool flag)
{
	if (mLinearFiltering != flag)
	{
		mCurrentTex = -1;
		mLinearFiltering = flag;
	}
}


void CCRenderBox::Init()
{

    float aspect = (float)mScreenWidth/(float)mScreenHeight;
	
    glViewport          (0, 0, mScreenWidth, mScreenHeight);
    glScissor           (0, 0, mScreenWidth, mScreenHeight);
	
    glMatrixMode        (GL_MODELVIEW);
    glLoadIdentity      ();
	
    glHint              (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	
    glShadeModel        (GL_SMOOTH);
    glDisable            (GL_DITHER);
    glClearColor        (0.0f, 0.0f, 0.0f, 0.0f );
	
    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);
	
	glEnable			(GL_TEXTURE_2D);
	
	glEnable			(GL_BLEND);
	glBlendFunc			(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	glEnable			(GL_SCISSOR_TEST);
	
    glMatrixMode        (GL_PROJECTION);
    glLoadIdentity      ();

    InitPerspective     (60.f, aspect, 0.1f, 1000.f);

}


void CCRenderBox::ShutDown()
{
	
}


void CCRenderBox::SetClip(int x, int y, int width, int height)
{
	Flush();
	glScissor(x, (mScreenHeight-y-height), width, height);
}


void CCRenderBox::NoClip()
{
	Flush();
	glScissor(0, 0, mScreenWidth, mScreenHeight);
}


void CCRenderBox::SwitchTo2D()
{
	Flush();
	
	glViewport				(0, 0, mScreenWidth, mScreenHeight);
	
	glMatrixMode			(GL_PROJECTION);
    glLoadIdentity			();
	glOrthof				(0, (float)mScreenWidth, 0, (float)mScreenHeight, 0, 100);
    glMatrixMode			(GL_MODELVIEW);
	glLoadIdentity			();
	
	glEnable				(GL_BLEND);
	glEnable				(GL_TEXTURE_2D);
	glDisable				(GL_CULL_FACE);
	glDisable				(GL_DEPTH_TEST);
	glDisable				(GL_LIGHTING);
	glDisableClientState	(GL_NORMAL_ARRAY);
	
}


void CCRenderBox::SwitchTo3D()
{
	Flush();
	
	float aspect = (float)mScreenWidth/(float)mScreenHeight;
	
	glViewport          (0, 0, mScreenWidth, mScreenHeight);
    glScissor           (0, 0, mScreenWidth, mScreenHeight);
	
    glMatrixMode        (GL_PROJECTION);
    glLoadIdentity      ();
    InitPerspective     (60.f, aspect, 0.1f, 1000.f);
    
    glMatrixMode        (GL_MODELVIEW);
    glLoadIdentity      ();

	glEnable			(GL_CULL_FACE);
	glEnable			(GL_DEPTH_TEST);
	glEnable			(GL_LIGHTING);
	glEnableClientState (GL_NORMAL_ARRAY);

}


void CCRenderBox::FillRect(int x, int y, int width, int height)
{
	Flush();
	
	y = mScreenHeight - y;
	
	short vertices[] = 
	{
		x,			y,			0,
		x,			y-height,	0,
		x+width,	y-height,	0,
		x+width,	y,			0
	};
	
	
	glDisable(GL_TEXTURE_2D);
	glDisableClientState (GL_TEXTURE_COORD_ARRAY);
	
	glVertexPointer(3, GL_SHORT, 0, vertices);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	
	glEnable(GL_TEXTURE_2D);
	glEnableClientState (GL_TEXTURE_COORD_ARRAY);
	
}


void CCRenderBox::DrawPolygon(float* x, float* y, int count)
{
	Flush();
	
	int idx = mVertexCount<<1;
	
	for(int i=0; i<count; i++)
	{
		mVertices[idx++] = x[i];
		mVertices[idx++] = mScreenHeight-y[i];
		
		mVertexCount++;
	}
	
	mVertices[idx++] = x[0];
	mVertices[idx++] = mScreenHeight-y[0];
	mVertexCount++;
	
	glDisable(GL_TEXTURE_2D);
	glDisableClientState (GL_TEXTURE_COORD_ARRAY);
	
	glVertexPointer(2, GL_FLOAT, 0, mVertices);
	glDrawArrays(GL_LINE_STRIP, 0, count+1);
	
	glEnable(GL_TEXTURE_2D);
	glEnableClientState (GL_TEXTURE_COORD_ARRAY);
	
	mVertexCount = 0;
	
}


void CCRenderBox::DrawCircle(float x, float y, float radius)
{
	Flush();
	
	int count = 32;
	float angle = 359;
	int idx = 0;
	for(int i=0; i<count; i++)
	{
		mVertices[idx++] = x+radius*cosf(angle*DEG2RAD);
		mVertices[idx++] = y+radius*sinf(angle*DEG2RAD);
		angle -= (360.0f/count);
		if (angle < 0.0f)
			angle = 0.0f;
	}
	
	mVertexCount = count;
	
	glDisable(GL_TEXTURE_2D);
	glDisableClientState (GL_TEXTURE_COORD_ARRAY);
	
	glVertexPointer(2, GL_FLOAT, 0, mVertices);
	glDrawArrays(GL_LINE_LOOP, 0, mVertexCount);
	
	glEnable(GL_TEXTURE_2D);
	glEnableClientState (GL_TEXTURE_COORD_ARRAY);
	
	mVertexCount = 0;
	
}


CCTexture* CCRenderBox::LoadTexture(const char* filename, const char *directory)
{
	
	const char *fullpath = GetPath(filename, directory);
	
	CCTexture *texture = CCTextureCreate(fullpath, true);
	
	return texture;
	
}



CCTexture* CCRenderBox::CreateTexture(int width, int height)
{
	CCTexture *tex = new CCTexture();
	
 	if (tex)
	{
		tex->mImageWidth = width;
		tex->mImageHeight = height;
		tex->mTextureWidth = width;
		tex->mTextureHeight = height;
		
		GLuint texId;
		
		glGenTextures(1, &texId);
		
		tex->mTextureId = texId;
		
		BindTexture(tex);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
		
		return tex;
		
	}
	
	return NULL;
}


void CCRenderBox::Flush()
{
	if (mVertexCount > 0)
	{
		glVertexPointer(2, GL_FLOAT, 0, mVertices);
		glTexCoordPointer(2, GL_FLOAT, 0, mUVs);
		
		if (mUseColorPointer)
			glColorPointer (4, GL_FLOAT, 0, mColors);
		
		glDrawArrays(GL_TRIANGLES, 0, mVertexCount);
		mVertexCount = 0;
	}
}


void CCRenderBox::BindTexture(CCTexture *texture)
{
	if (mCurrentTex != texture->mTextureId)
	{
		Flush();
		
		glBindTexture(GL_TEXTURE_2D, texture->mTextureId);
		mCurrentTex = texture->mTextureId;
		
		if (mLinearFiltering)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}
		else
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
	}
}


void CCRenderBox::RenderImage(CCImage* quad, Vertex2D* points)
{
	BindTexture(quad->mTexture);
	//int i = quad->mTexture->mTextureId;
	GLfloat uvs[] =
	{
		quad->mTX0, quad->mTY0,
		quad->mTX1, quad->mTY0,
		quad->mTX0, quad->mTY1,
		quad->mTX1, quad->mTY1
	};
	
	
	GLfloat vertices[] = 
	{
		points[0].x, mScreenHeight-points[0].y,
		points[1].x, mScreenHeight-points[1].y,
		points[2].x, mScreenHeight-points[2].y,
		points[3].x, mScreenHeight-points[3].y
		
	};
	
	
	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glTexCoordPointer(2, GL_FLOAT, 0, uvs);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	
}


void Swap(float *a, float *b)
{
	float n=*a;
	*a = *b;
	*b = n;
}

void CCRenderBox::RenderBlurImage(CCImage* quad,int times,float inc)
{
	float spost = 0.0f;					// 纹理坐标偏移量
	float alphainc = 0.9f / times;				// alpha混合的衰减量
	float alpha = 0.2f;					// Alpha初值
	
	glEnable(GL_TEXTURE_2D);					// 启用 2D 纹理映射
	glDisable(GL_DEPTH_TEST);					// 深度测试不可用
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);				// 设置混合模式
	glEnable(GL_BLEND);					// 启用混合
	//int i = quad->mTexture->mTextureId;
//	glBindTexture(GL_TEXTURE_2D,quad->mTexture->mTextureId);			// 绑定混合纹理
	BindTexture(quad->mTexture);
	
	alphainc = alpha / times;
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	
	for (int num = 0;num < times;num++)			// 着色模糊物的次数
	{
		glColor4f(1.0f, 1.0f, 1.0f, alpha);		// 调整alpha值
		//glColor4f(0.2f, 0.2f, 0.2f, 0);
		//glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
		float texcoord_data[]={
			//0+spost,1-spost,
//			0+spost,0+spost,
//			1-spost,0+spost,
//			1-spost,1-spost
			0+0.25*spost,1-spost,
			0+0.25*spost,0+spost,
			0.25-0.25*spost,0+spost,
			0.25-0.25*spost,1-spost
		};
		
		
		float vertex_data[]={
			50.0f,0.0f,
			50.0f,quad->mHeight*2.2,
			quad->mWidth+50,quad->mHeight*2.2,
			quad->mWidth+50,0.0f
			//50.0f,0.0f,
//			50.0f,quad->mHeight,
//			quad->mWidth+50,quad->mHeight,
//			quad->mWidth+50,0.0f
		};
		
		
		glTexCoordPointer(2,GL_FLOAT,0,texcoord_data);
		glVertexPointer(2,GL_FLOAT,0,vertex_data);
		glDrawArrays(GL_TRIANGLE_FAN,0,4);
		
		spost += inc;				// 逐渐增加 spost (快速靠近纹理中心)
		alpha = alpha - alphainc;			// 逐渐增加 alpha (逐渐淡出纹理)
	}
	
	glEnable(GL_DEPTH_TEST);					// 深度测试可用
//	glDisable(GL_TEXTURE_2D);					// 2D纹理映射不可用
	glDisable(GL_BLEND);					// 混合不可用
	//glBindTexture(GL_TEXTURE_2D,0);
}

void CCRenderBox::RenderImage(CCImage* quad, float xo, float yo, float angle, float xScale, float yScale)
{
	BindTexture(quad->mTexture);
	//int i = quad->mTexture->mTextureId;
	yo = mScreenHeight - yo;
	
	float width = quad->mWidth*xScale;
	float height = quad->mHeight*yScale;
	float x = xo - quad->mAnchorX*xScale;
	float y = yo + quad->mAnchorY*yScale;
	
	float xx, yy;
	float cosAngle = cosf(angle);
	float sinAngle = sinf(angle);
	
	GLfloat uvs[] =
	{
		quad->mTX0, quad->mTY0,
		quad->mTX1, quad->mTY0,
		quad->mTX0, quad->mTY1,
		quad->mTX1, quad->mTY1,
		quad->mTX0, quad->mTY1,
		quad->mTX1, quad->mTY0
	};
	
	
	if (quad->mHFlipped)
	{
		uvs[ 0] = quad->mTX1;
		uvs[ 2] = quad->mTX0;
		uvs[ 4] = quad->mTX1;
		uvs[ 6] = quad->mTX0;
		uvs[ 8] = quad->mTX1;
		uvs[10] = quad->mTX0;
	}
	
	if (quad->mVFlipped)
	{
		uvs[ 1] = quad->mTY1;
		uvs[ 3] = quad->mTY1;
		uvs[ 5] = quad->mTY0;
		uvs[ 7] = quad->mTY0;
		uvs[ 9] = quad->mTY0;
		uvs[11] = quad->mTY1;
	}
	
	GLfloat vertices[] = 
	{
		x,			y,
		x+width,	y,
		x,			y-height,
		x+width,	y-height,
		0, 0,
		0, 0
	};
	
	int idx = 8;
	if (angle != 0.0f)
	{
		idx = 0;
		
		for (int i=0;i<4;i++)
		{
			xx = (cosAngle*(vertices[idx]-xo) - sinAngle*(vertices[idx+1]-yo) + xo);
			yy = (sinAngle*(vertices[idx]-xo) + cosAngle*(vertices[idx+1]-yo) + yo); 
			
			vertices[idx] = xx;
			vertices[idx+1] = yy;
			idx += 2;
		}
		
	}
	
	vertices[idx++] = vertices[4];
	vertices[idx++] = vertices[5];
	
	vertices[idx++] = vertices[2];
	vertices[idx++] = vertices[3];
	
	if (mVertexCount >= MAX_VERTEX_BUFFER-6)
		Flush();
	

	int u = 0;
	idx = 0;
	for (int i=0;i<6;i++)
	{
		mVertices[(mVertexCount<<1)] = vertices[idx++];
		mVertices[(mVertexCount<<1)+1] = vertices[idx++];
		mUVs[(mVertexCount<<1)] = uvs[u++];
		mUVs[(mVertexCount<<1)+1] = uvs[u++];
		if (mUseColorPointer)
		{
			mColors[(mVertexCount<<2)] = quad->mColor.r;
			mColors[(mVertexCount<<2)+1] = quad->mColor.g;
			mColors[(mVertexCount<<2)+2] = quad->mColor.b;
			mColors[(mVertexCount<<2)+3] = quad->mColor.a;
		}
		mVertexCount++;
		
	}
	
}


void CCRenderBox::SetColor(float r, float g, float b, float a)
{
	Flush();
	
	glColor4f(r, g, b, a);
}


void CCRenderBox::EnableColorPointer(bool flag)
{
	if (flag)
	{
		mUseColorPointer = true;
		glEnableClientState(GL_COLOR_ARRAY);
	}
	else
	{
		mUseColorPointer = false;
		glDisableClientState(GL_COLOR_ARRAY);
	}
}


void CCRenderBox::EnableAddictiveDraw(bool flag)
{
	Flush();
	
	if (flag)
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	else
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
}



void CCRenderBox::RenderBegin()
{
	glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
	
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	
}


void CCRenderBox::RenderEnd()
{
	Flush();
}
